/*
 * @Description: Modbus RTU Slave
 * @Author: Dryad
 * @Date: 2019-06-09 21:59:58
 */
#include <rtthread.h>
#include <stm32f4xx_tim.h>
#include <misc.h>
#include <rtdevice.h>
#include "../../modbus_slave_core.h"
#include "./modbus_slave.h"
#include "../modbus_CRC16.h"

#define LOG_TAG "modbus rtu slave" /* 该模块TAG */
#define LOG_LVL LOG_LVL_DBG        /* 静态过滤级别为调试级 */
#include <ulog.h>                  /* 必须放在宏定义下面 */

/* 通信用相关串口和定时器等 */
static rt_size_t modbus_slave_rx_length;        /* Modbus从机接收到的数据长度 */
static char modbus_slave_rx_data[512];          /* Modbus从机接收数据暂存 */
static char modbus_slave_tx_data[512];          /* Modbus从机发送数据暂存 */
static struct rt_semaphore modbus_slave_rx_sem; /* 接收到应答数据信号量 */
static TIM_TypeDef *const tim = TIM11;          /* 超时检测定时器为TIM11 */

static void Modbus_Slave_Thread(void *parameter);                          /* Modbus Slave线程 */
static rt_err_t Modbus_Slave_RX_CallBack(rt_device_t dev, rt_size_t size); /* Modbus Slave接收回调函数 */
static struct rt_thread modbus_slave_thread;                               /* Modbus Slave线程句柄 */
ALIGN(RT_ALIGN_SIZE)                                                       /* 线程栈4字节对齐 */
static char modbus_slave_thread_stack[2048];                               /* 线程栈起始地址 */
static rt_device_t modbus_slave_dev = RT_NULL;                             /* 串口设备句柄 */
extern "C" void TIM1_TRG_COM_TIM11_IRQHandler(void);                       /* TIM11中断函数，用于判断Modbus RTU数据帧结束 */

static bool Parse_RX(const char *receive_data, unsigned int receive_length); /* 解析接收到的数据帧 */

/*保存接收数据中的Modbus关键字*/
static char func_code = 0;    /* 功能码 */
static const char *func_data; /* 数据域 */
static int func_data_length;  /* 数据域长度 */
static char slave_addr;       /* 从机地址 */

void Modbus_RTU_Slave_Thread_Init(unsigned long baudrate, char slave_id)
{
    rt_err_t rt_result = RT_EOK;
    modbus_slave_dev = rt_device_find(ModbusRTU_Slave_DEVICE_NAME); /* ModbusRTU Slave设备名称 */
    slave_addr = slave_id;

    if (modbus_slave_dev == RT_NULL)
    {
        rt_kprintf("Modbus Slave Uart Error\n");
        return;
    }

    /* 设置波特率 */
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    config.baud_rate = baudrate;
    config.bufsz = 512;
    rt_device_control(modbus_slave_dev, RT_DEVICE_CTRL_CONFIG, &config);

    rt_device_open(modbus_slave_dev, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_DMA_TX | RT_DEVICE_FLAG_INT_RX); /*DMA发送，中断接收打开串口*/

    /* 初始化Modbus Slave接收数据信号量，初始值为0，接收到数据后释放信号量 */
    rt_sem_init(&modbus_slave_rx_sem, "modbus rtu slave rx sem", 0, RT_IPC_FLAG_FIFO);
    rt_device_set_rx_indicate(modbus_slave_dev, Modbus_Slave_RX_CallBack);

    /* 设置Modbus数据帧检测定时器 */
    {
        rt_uint16_t usTimer_T35 = 0;
        /* 根据不同波特率设置t3.5时间 */
        if (baudrate > 19200)
        {
            usTimer_T35 = 35; /* 波特率大于19200，超时时间为1.75ms */
        }
        else
        {
            /* The timer reload value for a character is given by:
		    *
		    * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
		    *             = 11 * Ticks_per_1s / Baudrate
		    *             = 220000 / Baudrate
		    * The reload for t3.5 is 1.5 times this value and similary
		    * for t3.5.
		    */
            usTimer_T35 = (7UL * 220000UL) / (2UL * baudrate); /* 波特率小于等于19200，超时时间为3.5个字符 */
        }

        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;

        /* 设置定时器时基20KHz */
        TIM_TimeBaseStructure.TIM_Period = usTimer_T35 - 1;
        TIM_TimeBaseStructure.TIM_Prescaler = 8400 - 1;
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit(tim, &TIM_TimeBaseStructure);

        tim->CR1 &= ~(1 << 1);    //清空UDIS,使能更新中断UEV
        tim->CR1 |= (1 << 2);     //置位URS,只有计数器上溢会生成UEV（更新中断）
        tim->SR = ~TIM_IT_Update; //清零更新中断标志

        tim->DIER |= TIM_IT_Update; //使能更新中断

        NVIC_InitStructure.NVIC_IRQChannel = TIM1_TRG_COM_TIM11_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;        //响应优先级
        NVIC_Init(&NVIC_InitStructure);
    }

    /* Modbus Slave线程初始化 */
    rt_result = rt_thread_init(&modbus_slave_thread, "modbus slave thread", Modbus_Slave_Thread, RT_NULL, modbus_slave_thread_stack, sizeof(modbus_slave_thread_stack), 8, 2);
    if (rt_result == RT_EOK)
    {
        rt_thread_startup(&modbus_slave_thread); /* 启动线程 */
    }
    else
    {
        rt_kprintf("Modbus Slave thread error\n");
    }
}

void Modbus_Slave_Thread(void *parameter)
{
    rt_err_t rt_result = RT_EOK;
    rt_size_t read_size = 0, tx_length = 0;

    while (1)
    {
        rt_result = rt_sem_take(&modbus_slave_rx_sem, RT_WAITING_FOREVER); /* 等待接收数据 */

        /* 错误，等待下一次接收 */
        if (rt_result != RT_EOK)
        {
            continue;
        }

        /* 长度限幅 */
        if (modbus_slave_rx_length > sizeof(modbus_slave_rx_data))
        {
            modbus_slave_rx_length = sizeof(modbus_slave_rx_data);
        }
        read_size = rt_device_read(modbus_slave_dev, 0, modbus_slave_rx_data, modbus_slave_rx_length); /* 读取数据 */
        bool parse_result = Parse_RX(modbus_slave_rx_data, read_size);                                 /* 解析数据 */
        if (parse_result)
        {
            modbus_slave_tx_data[0] = slave_addr; /* 保存从机地址 */
            tx_length = Modbus_Slave_Core(func_code, func_data, func_data_length, &modbus_slave_tx_data[1]);
            if (tx_length > 0)
            {
                tx_length++; /* 长度包括从机地址 */
                unsigned crc16_temp = CRC16_Cal((const unsigned char *)modbus_slave_tx_data, tx_length);
                modbus_slave_tx_data[tx_length] = crc16_temp & 0x00FF;
                modbus_slave_tx_data[tx_length + 1] = crc16_temp >> 8;
                tx_length += 2;
                rt_device_write(modbus_slave_dev, 0, modbus_slave_tx_data, tx_length);
            }
        }
    }
}

rt_err_t Modbus_Slave_RX_CallBack(rt_device_t dev, rt_size_t size)
{
    /* 保存接收数据长度 */
    modbus_slave_rx_length = size;
    tim->CNT = 0;            /* 计数器清0 */
    tim->CR1 |= TIM_CR1_CEN; /* 使能定时器 */
    return RT_EOK;
}

/**
 * @description: 定时器11中断服务函数，用于检测串口通信数据帧结束
 * @param {type}
 * @return:
 */
void TIM1_TRG_COM_TIM11_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();
    if (TIM11->SR & TIM_IT_Update) //更新中断
    {
        /* 定时器更新中断 */
        TIM11->SR = ~TIM_IT_Update;
        TIM11->CR1 &= ~TIM_CR1_CEN;           /* 关闭定时器 */
        rt_sem_release(&modbus_slave_rx_sem); /* 释放信号量，通知处理线程 */
    }
    /* leave interrupt */
    rt_interrupt_leave();
}

bool Parse_RX(const char *receive_data, unsigned int receive_length)
{
    int min_length = receive_length - 8;
    for (int i = 0; i <= min_length; i++)
    {
        /* 检测到了数据帧头 */
        if (receive_data[0] == slave_addr)
        {
            char func_cmd = receive_data[1]; /* 保存功能码 */
            int crc16_length = 0;
            switch (func_cmd)
            {
            case 0x03: /* 读多个寄存器 */
                func_data_length = 4;
                crc16_length = func_data_length + 2;
                break;
            case 0x06: /* 写单个寄存器 */
                func_data_length = 4;
                crc16_length = func_data_length + 2;
                break;
            case 0x10: /* 写多个寄存器 */
            {
                int data_size = receive_data[6];
                func_data_length = data_size + 5;
                crc16_length = func_data_length + 2;
            }
            break;
            default: /* 不支持该功能码，下一个循环 */
                receive_data++;
                continue;
            }

            /*  */
            if (crc16_length + 2 > receive_length)
            {
                return false;
            }
            /*校验通过*/
            else if (CRC16_Cal((const unsigned char *)receive_data, crc16_length + 2) == 0x0000)
            {
                func_code = func_cmd;         /* 保存功能码 */
                func_data = receive_data + 2; /* 保存数据域地址 */
                return true;
            }
        }
        receive_data++;
    }
    return false;
}
